#include "process_protocol.h"
using namespace secret_proc_protocol;
protocolException::protocolException():errmsg(PROC_NONE)
{
    errmsg_str = "no error";
}

/**
 * @brief Error 
 * 
 * 
 * 
 **/
protocolException::protocolException(const protocolErrMsg error_message, const int proc_errno):errmsg(error_message), proc_errno(proc_errno)
{
    switch (errmsg)
    {
    case PROC_BAD_MKFIFO:
        errmsg_str.assign(std::strerror(proc_errno));
        errmsg_str.append(" error in mkfifo()");
        break;
    case PROC_BAD_OPEN:
        errmsg_str.assign(std::strerror(proc_errno));
        errmsg_str.append(" error in open()");
        break;
    case PROC_BAD_CLOSE:
        errmsg_str.assign(std::strerror(proc_errno));
        errmsg_str.append(" error in close()");
        break;
    case PROC_BAD_WRITE:
        errmsg_str.assign(std::strerror(proc_errno));
        errmsg_str.append(" error in write()");
        break;
    case PROC_BAD_READ:
        errmsg_str.assign(std::strerror(proc_errno));
        errmsg_str.append(" error in read()");
        break;
    case PROC_BAD_FILETYPE:
        errmsg_str.assign(std::strerror(proc_errno));
        errmsg_str.append(" unknown file type");
        break;
    default:
        errmsg_str = "no error";
        break;
    }
}

const std::string protocolException::what() const
{
    return errmsg_str;
}

/**
 * @brief initialize procProtocol
 * @param write_filename the fifo file to write you string
 * @param read_filename the fifo file to read string from another process
 * @param RAII_remove remove pipe file in deconstruction
 * @author Ethan Yao
 **/
procProtocol::procProtocol(const std::string &write_filename, \
            const std::string &read_filename, bool RAII_remove)
{   // 先判断是否有该文件以及文件类型是否匹配
    if(stat(write_filename.c_str(), &server_stat) == 0)
    {   // if the file exist, don't try to create it.
        if(S_ISFIFO(client_stat.st_mode))
        {
            this->server_filename = write_filename;
            this->RAII_remove = RAII_remove;
        }
        else
        {
            throw protocolException(PROC_BAD_FILETYPE, errno);
        }
    }
    // S_IRUSR : readable for file owner;(defined in stat.h)
    // S_IWUSR : writable for file owner
    // for more information, type man 2 stat in your terminal
    else if(mkfifo(write_filename.c_str(), S_IRUSR|S_IWUSR) == 0)
    {   // create the corresponding file
        this->server_filename = write_filename;
        this->RAII_remove = RAII_remove;
    }
    else
    {
        throw protocolException(PROC_BAD_MKFIFO, errno);
    }
    
    if(stat(read_filename.c_str(), &client_stat) == 0)
    {   // if the file exist, don't try to create it.
        if(S_ISFIFO(client_stat.st_mode))
        {
            this->client_filename = read_filename;
            this->RAII_remove = RAII_remove;
        }
        else
        {
            throw protocolException(PROC_BAD_FILETYPE, errno);
        }
    }
    else if(mkfifo(read_filename.c_str(), S_IRUSR|S_IWUSR) == 0)
    {   // create the corresponding file
        this->client_filename = read_filename;
        this->RAII_remove = RAII_remove;
    }
    else
    {
        throw protocolException(PROC_BAD_MKFIFO, errno);
    }
    this->is_init = true;
    this->buffer.resize(BUFF_SIZE);
}

procProtocol::~procProtocol()
{
    if(RAII_remove)
    {   // 每一个Server负责删除对应的FIFO文件，不应让Server和Client同时操作
        if(stat(server_filename.c_str(), &server_stat) == 0)
        {
            if(unlink(server_filename.c_str()) == -1)
                fprintf(stderr, "%s error in server file unlink()\n", std::strerror(errno));
        }
    }
}

/**
 * @brief Providing a more flexable initialization way(without RAII).
 * 
 * 
 **/
void procProtocol::init(const std::string& write_filename, const std::string& read_filename)
{
    if(is_init)
        return ;
    if(stat(read_filename.c_str(), &client_stat) == 0)
    {   // if the file exist, don't try to create it.
        if(S_ISFIFO(client_stat.st_mode))
        {
            this->client_filename = read_filename;
        }
        else
        {
            throw protocolException(PROC_BAD_FILETYPE, errno);
        }
    }
    else if(mkfifo(read_filename.c_str(), S_IRUSR|S_IWUSR) == 0)
    {
        this->client_filename = read_filename;
    }
    else
    {
        throw protocolException(PROC_BAD_MKFIFO, errno);
    }

    if(stat(write_filename.c_str(), &server_stat) == 0)
    {
        if(S_ISFIFO(server_stat.st_mode))
        {
            this->server_filename = write_filename;
        }
        else
        {
            throw protocolException(PROC_BAD_FILETYPE, errno);
        }
    }    
    else if(mkfifo(write_filename.c_str(), S_IRUSR|S_IWUSR) == 0)
    {
        this->server_filename = write_filename;
    }
    else
    {
        throw protocolException(PROC_BAD_MKFIFO, errno);
    }
    this->is_init = true;
    this->buffer.resize(BUFF_SIZE);
}

std::string procProtocol::readPipe(accessMode mode)
{
    switch (mode)
    {
    case BLOCKED:
        fd = open(client_filename.c_str(), O_RDONLY);
        break;
    case UNBLOCKED:
        fd = open(client_filename.c_str(), O_RDONLY|O_NONBLOCK);
        break;
    case ASYNCHRONIZED:
        fd = open(client_filename.c_str(), O_RDONLY|O_ASYNC);
        break;
    }
    if(fd == -1)
    {
        throw protocolException(PROC_BAD_OPEN, errno);
    }
    if(buffer.size() != BUFF_SIZE)
        buffer.resize(BUFF_SIZE);
    ch_num = read(fd, (void*)buffer.c_str(), BUFF_SIZE-1);
    if(ch_num == -1)
    {
        throw protocolException(PROC_BAD_READ, errno);
    }
    fd = close(fd);
    if(fd == -1)
    {
        throw protocolException(PROC_BAD_CLOSE, errno);
    }
    buffer.resize(ch_num);  // note: \0 is not calculated in the size
    return buffer;
}

void procProtocol::writePipe(const std::string& input, accessMode mode)
{
    switch (mode)
    {
    case BLOCKED:
        fd = open(server_filename.c_str(), O_WRONLY);
        break;
    case UNBLOCKED:
        fd = open(server_filename.c_str(), O_WRONLY|O_NONBLOCK);
        break;
    case ASYNCHRONIZED:
        fd = open(server_filename.c_str(), O_WRONLY|O_ASYNC);
        break;
    }
    if(fd == -1)
    {
        throw protocolException(PROC_BAD_OPEN, errno);
    }
    ch_num = write(fd, input.c_str(), input.size()+1);
    if(ch_num == -1)
    {
        throw protocolException(PROC_BAD_WRITE, errno);
    }
    fd = close(fd);
    if(fd == -1)
    {
        throw protocolException(PROC_BAD_CLOSE, errno);
    }
}

communicator::~communicator()
{
    read_stop = true;
    write_stop = true;
    read_thread.join();     // 无需对线程是否joinable进行判断
    write_thread.join();
}

void communicator::createReadThread()
{
    std::string temp_read;
    bool stop=false;
    read_thread = std::thread([&]()
    {
        read_mutex.lock();
        stop = read_stop;
        read_mutex.unlock();
        while(!stop)
        {
            temp_read = readPipe();
            read_mutex.lock();
            read_msg.assign(temp_read);
            stop = read_stop;
            read_mutex.unlock();
        }
    });
}

void communicator::createWriteThread()
{
    std::string temp_write;
    bool stop=false;
    write_thread = std::thread([&]()
    {
        write_mutex.lock();
        stop = write_stop;
        temp_write.assign(write_msg);
        write_mutex.unlock();
        while(!stop)
        {
            writePipe(temp_write);
            write_mutex.lock();
            temp_write.assign(write_msg);
            stop = write_stop;
            write_mutex.unlock(); 
        }
    });
}

// 现在还没有实现在read瞬间获取其内容，通过信号加回调函数可以异步获取内容
// 唯一的问题是信号是不排队的，一旦错过一个信号，就相当于错过了一个read获取的值
// 所以意味着回调函数不能写成需要太长运算时间的，否则将导致数据丢包
// 在绝大多数情况下，都可以不使用这个多线程的支持，手写多线程，在多线程的函数中更新对应的数据即可
// 也就是说在绝大多数情况下丢包对于视觉没有影响